/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
#ifndef __CONTEXT5_H__
#define __CONTEXT5_H__

#include <assert.h>
#include <string.h>
#include "../base/context_x.h"
#include "stackmap_5.h"
#include "instr_props_5.h"

static const short MARK_SUBROUTINE_DONE = -1;

//
// Context - main class of Type Checker
//
class vf_Context_5 : public vf_Context_x<vf_Context_5, WorkmapElement_5, _WorkmapElement_5, StackmapElement_5> {
public:
    vf_Context_5(SharedClasswideData &classwide) :
      vf_Context_x<vf_Context_5, WorkmapElement_5, _WorkmapElement_5, StackmapElement_5>(classwide) {
          stackmapattr_calculation = false;
      }

      vf_Result verify_method(Method_Handle method);
protected:
    // various flags for all the method's bytecode instructions
    InstrProps props;

    // stack to push instructions like branch targets, etc to go thru the method. the stack is method-wide.
    MarkableStack stack;

    //we would like to flush StackMapTable attribute from this method
    bool      stackmapattr_calculation;

    void mark_stackmap_point(Address target) {
        //in case we prepare for flushing stackmaptable attrribute
        //make sure we avoid optimization and mark all targets as multiway
        if( stackmapattr_calculation ) stack.push(target);
    }

    //init method-wide data
    void init(Method_Handle _m_method) {
        vf_Context_x<vf_Context_5, WorkmapElement_5, _WorkmapElement_5, StackmapElement_5>::init(_m_method);
        stack.init();
        props.init(mem, m_code_length);
    }

    // load derived types previously stored for the given instruction
    void fill_workmap(Address instr) {
        PropsHead_5 *head = (PropsHead_5*)props.getInstrProps(instr);
        if( head->is_workmap() ) {
            tc_memcpy(workmap, head->getWorkmap(), sizeof(WorkmapHead) + sizeof(WorkmapElement_5) * (m_stack_start + head->workmap.depth));
        } else {
            StackmapHead *stackmap = head->getStackmap();

            workmap->depth = stackmap->depth;

            for( unsigned i = 0; i < m_stack_start + stackmap->depth; i++) {
                workmap->elements[i] = _WorkmapElement_5(&stackmap->elements[i]);
                assert( workmap->elements[i].getAnyPossibleValue() != SM_NONE );
            }
        }
        no_locals_info = 1;
    }

    //store a copy of the current workmap for another instruction (such as a branch target)
    void storeWorkmapCopy(Address target) {
        int sz = m_stack_start + workmap->depth;
        PropsHead_5* copy = newWorkmapProps(sz);
        tc_memcpy(copy->getWorkmap(), workmap, sizeof(WorkmapHead) + sizeof(WorkmapElement_5) * sz);

        props.setInstrProps(target, copy);
    }

    //create a stackmap vector of the given size sz (max_locals <= sz <= max_locals+max_stack)
    PropsHead_5* newStackmap(int sz) {
        return (PropsHead_5*)mem.calloc(sizeof(PropsHead_5) + sizeof(StackmapElement_5) * sz);
    }

    //create a vector that will be used for JSR procesing. 
    //It contains ether stackmap or workmap vector, SubrouitineData, and flags vector indicating 
    //changed locals
    PropsHead_5 *newRetData() {
        assert( sizeof(StackmapElement_5) >= sizeof(WorkmapElement_5) );

        int sz = sizeof(PropsHead_5) + sizeof(StackmapElement_5) * (m_max_stack + m_stack_start) + //stackmap
            ((sizeof(SubroutineData)+ m_stack_start) & (~3)) + 4; // fixed data and changed locals vector

        PropsHead_5 * ret = (PropsHead_5 *) mem.calloc(sz);
        ret->set_as_workmap();
        return ret;
    }

    //creates a temporary variable for converting 
    StackmapElement_5 *new_variable() {
        return (StackmapElement_5*) mem.calloc(sizeof(StackmapElement_5));
    }

    /////////////////////////////////////////////////////////////////////////////////////////////////////

    //First verification pass thru the method. checks that no jump outside the method or to the middle of instruction
    //checks that opcodes are valid
    vf_Result parse(Address instr, int dead_code_parsing, FastStack<Address> *deadstack);

    //Second pass: dataflow of a piece of the method starting from the beginning or a branch target and finishing
    //on return, athrow or hitting previously passed instruction. 
    //This function initializes workmap and calls DataflowLoop
    vf_Result StartLinearDataflow(Address start);

    //Second pass: Finilize subroutie processing -- once we are here, then all the RETs from achievable for
    //the given subroutine are passed, so we can resume passing for JSRs to the given address
    //This function initializes workmap properly and calls DataflowLoop
    vf_Result SubroutineDone(Address start);

    //Second pass: dataflow of a piece of the method starting from the beginning or a branch target and finishing
    //on return, athrow or hitting previously passed instruction
    vf_Result DataflowLoop(Address start, int workmap_is_a_copy_of_stackmap);

    //constraint propagation
    vf_Result propagate(StackmapElement_5 *changed, SmConstant new_value);

    //update current derived types according to what was changed in subroutine
    void restore_workmap_after_jsr(Address jsr_target);

    //create vector constraints for each target of a switch
    vf_Result processSwitchTarget(Address target) {
        vf_Result tcr;
        if( props.isMultiway(target) ) {
            if( (tcr=new_generic_vector_constraint(target)) != VF_OK ) {
                return tcr;
            }

            if( !props.isDataflowPassed(target) ) {
                stack.xPush(target);
            }
        } else {
            assert( !props.isDataflowPassed(target) );
            storeWorkmapCopy(target);

            stack.xPush(target);
        }
        return VF_OK;
    }

    ///////////////////////////////////  "VIRTUAL" METHODS /////////////////////////////////////////////
public:
    //create constraint vector in case of a branch 
    //simple conatraints are created for pairs of both locals and stack (current must be assignable to target)
    vf_Result new_generic_vector_constraint(Address target_instr) {
        return new_generic_vector_constraint_impl(getStackmap(target_instr, workmap->depth));
    }

    //when we hit RET instruction we update the data for the given subroutine with current derived types
    vf_Result new_ret_vector_constraint(Address target_instr);

    // push catch-block to the stack of branches to pass
    void push_handler(Address handler_pc) {
        if( !props.isDataflowPassed(handler_pc) ) {
            stack.xPush(handler_pc);
        }
    }

    //create simple single constraint: "'from' is assingable to 'to'"
    vf_Result new_scalar_constraint(WorkmapElement_5 *from, StackmapElement_5 *to);

    //add one more possible value (type) that can come to the given point (local or stack)
    vf_Result add_incoming_value(SmConstant new_value, StackmapElement_5 *destination);

    //create stackmap for exception handler start
    void createHandlerStackmap(Address handler_pc, SmConstant type) {
        StackmapHead *map = getStackmap(handler_pc, 1);
        //handler stackmaps are created before any dataflow analysis is done
        assert(map->depth == 0 || map->depth == 1);
        map->depth = 1;

        vf_Result tcr = add_incoming_value(type, &map->elements[m_stack_start]);

        // it is initialization stage
        assert(tcr == VF_OK);
    }


    //create a workmap vector for the given size sz (max_locals <= sz <= max_locals+max_stack)
    PropsHead_5 *newWorkmapProps(int sz) {
        PropsHead_5 * ret = (PropsHead_5*)mem.malloc(sizeof(PropsHead_5) + sizeof(WorkmapElement_5) * sz);
        ret->set_as_workmap();
        return ret;
    }

    //returns stackmap for the 'instr' instruction
    //if it does not exists yet -- create it. When created use 'depth' as stack depth
    StackmapHead *getStackmap(Address instr, int depth) {
        PropsHead_5 *pro = (PropsHead_5*) props.getInstrProps(instr);
        if( !pro ) {
            pro = newStackmap(m_stack_start + depth);
            props.setInstrProps(instr, pro);
            pro->getStackmap()->depth = depth;
        }
        return pro->getStackmap();
    }

    //returns stackmap for the 'instr' instruction. it must exist
    StackmapHead *getStackmap(Address instr) {
        PropsHead_5 *pro = (PropsHead_5*)props.getInstrProps(instr);
        assert(pro);
        return pro->getStackmap();
    }

    /////////////// expect some type //////////////

    //expect exactly this type
    int workmap_expect_strict( WorkmapElement_5 &el, SmConstant type ) {
        assert(type != SM_BOGUS);

        if( !el.isVariable() ) {
            return type == el.getConst();
        }

        IncomingType *in = el.getVariable()->firstIncoming();
        while( in ) {
            if( type != in->value ) {
                return false;
            }
            in = in->next();
        }

        ExpectedType *exp = el.getVariable()->firstExpected();
        while( exp ) {
            if( type == exp->value ) {
                return true;
            }
            exp = exp->next();
        }

        el.getVariable()->newExpectedType(&mem, type);

        return true;
    }

    int workmap_expect( WorkmapElement_5 &el, SmConstant type ) {
        if( !el.isVariable() ) {
            return tpool.mustbe_assignable(el.getConst(), type);
        } else {
            ExpectedType* exp = el.getVariable()->firstExpected();
            while( exp ) {
                if( type == exp->value ) {
                    return true;
                }
                exp = exp->next();
            }

            IncomingType *in = el.getVariable()->firstIncoming();
            //check that all existing incoming type are assignable to the new expected type
            while( in ) {
                if( !tpool.mustbe_assignable(in->value, type) ) {
                    return false;
                }
                in = in->next();
            }
            //add the new expected type
            el.getVariable()->newExpectedType(&mem, type);
        }
        return true;
    }

    //create special type of conatraint: "'from' is an array and it's element is assignable to 'to'"
    vf_Result new_scalar_array2ref_constraint(WorkmapElement_5 *from, WorkmapElement_5 *to) {
        if( !from->isVariable() ) {
            //although new_scalar_conatraint() whould process from constants correctly 
            // we just do not need new variable if it is really a constant
            *to = _WorkmapElement_5( tpool.get_ref_from_array(from->getConst()) );
            return VF_OK;
        }
        assert( from->isVariable() );

        ArrayCnstr* arr = from->getVariable()->firstArrayCnstr();
        //at most one array conversion constraint per variable is possible
        if( arr ) {
            *to = _WorkmapElement_5(arr->variable);
            return VF_OK;
        }

        *to = _WorkmapElement_5( new_variable() );

        IncomingType *inc = from->getVariable()->firstIncoming();
        from->getVariable()->newArrayConversionConstraint(&mem, to->getVariable());

        while( inc ) {
            SmConstant inc_val = tpool.get_ref_from_array(inc->value);
            vf_Result vcr = add_incoming_value( inc_val, to->getVariable() );
            if( vcr != VF_OK ) {
                return vcr;
            }
            inc = inc->next();
        }
        return VF_OK;
    }

    void new_bogus_propagation_constraint(WorkmapElement_5 &wm_el, SmConstant init_val) {
        if( !wm_el.isVariable() ) {
            wm_el = _WorkmapElement_5 (init_val);
        } else {

            WorkmapElement_5 wm_init = _WorkmapElement_5 (new_variable());
            wm_init.var_ptr->newIncomingType(&mem, init_val);
            wm_el.getVariable()->newGenericConstraint(&mem, wm_init.getVariable());
            wm_el = wm_init;
        }
    }
};

#endif
